<script setup lang="ts">
import { onMounted, onUnmounted, ref, shallowRef } from 'vue'
import {
  AxesHelper,
  Scene,
  PerspectiveCamera,
  WebGLRenderer,
  Mesh,
  PlaneGeometry,
  MeshBasicMaterial,
  ShaderMaterial,
  DoubleSide,
  Clock,
  DirectionalLight,
  MeshStandardMaterial,
  Object3D,
  BufferGeometry,
  TextureLoader,
  MeshDepthMaterial,
  RGBADepthPacking,
} from 'three'
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls'
import { GUI } from 'dat.gui'
import Stats from 'three/examples/jsm/libs/stats.module'
import planeFrag from './plane.frag?raw'
import planeVert from './plane.vert?raw'
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader'
import leePerrySmithGLB from './LeePerrySmith/LeePerrySmith.glb?url'
import colorImg from './LeePerrySmith/color.jpg'
import normalImg from './LeePerrySmith/normal.jpg'

const contentRef = ref<HTMLCanvasElement>()
const renderer = shallowRef<WebGLRenderer>(new WebGLRenderer())
const orbitControls = shallowRef<OrbitControls>()
const scene = new Scene()
const camera = shallowRef<PerspectiveCamera>()
const axesHelper = new AxesHelper(20)
const clock = new Clock()
// 创建性能监视器
let stats = new Stats()

const customUniform = {
  uTime: {
    value: 0,
  },
}

const planeShaderMaterial = new ShaderMaterial({
  vertexShader: planeVert,
  fragmentShader: planeFrag,
  uniforms: {
    uElapsedTime: {
      value: 0,
    },
  },
  // wireframe: true,
  side: DoubleSide,
})

const gui = new GUI()

const init = () => {
  if (contentRef.value) {
    camera.value = new PerspectiveCamera(75, contentRef.value.clientWidth / contentRef.value.clientHeight)
    camera.value.position.set(0, 0, 20)
    scene.add(camera.value)
    scene.add(axesHelper)
    contentRef.value.appendChild(renderer.value.domElement)

    stats.dom.style.position = 'absolute'
    stats.dom.style.top = '0'
    stats.dom.style.left = '0'
    contentRef.value.appendChild(stats.dom)

    renderer.value.setSize(contentRef.value?.clientWidth, contentRef.value?.clientHeight)

    orbitControls.value = new OrbitControls(camera.value, renderer.value.domElement)
    orbitControls.value.enableDamping = true
  }
}

const render = () => {
  const elapsedTime = clock.getElapsedTime()
  planeShaderMaterial.uniforms.uElapsedTime.value = elapsedTime
  customUniform.uTime.value = elapsedTime

  if (camera.value) {
    orbitControls.value?.update()
    renderer.value?.render(scene, camera.value)
  }
  stats.update()
  requestAnimationFrame(render)
}

window.addEventListener('resize', () => {
  if (contentRef.value && camera.value) {
    camera.value.aspect = contentRef.value.clientWidth / contentRef.value.clientHeight
    camera.value.updateProjectionMatrix()

    renderer.value.setSize(contentRef.value?.clientWidth, contentRef.value?.clientHeight)
    renderer.value.setPixelRatio(window.devicePixelRatio)
  }
})

onMounted(async () => {
  init()

  const directionalLight = new DirectionalLight('#fff', 1.2)
  directionalLight.position.set(0, 0, 20)

  const planeGeometry = new PlaneGeometry(20, 15)
  const planeMaterial = new MeshStandardMaterial({ color: '#fff' })

  const planeMesh = new Mesh(planeGeometry, planeMaterial)

  const gltfLoader = new GLTFLoader()
  const textureLoader = new TextureLoader()
  const { scene: leePerrySmithModels } = await gltfLoader.loadAsync(leePerrySmithGLB)
  const colorTexture = await textureLoader.loadAsync(colorImg)
  const normalTexture = await textureLoader.loadAsync(normalImg)

  const material = new MeshStandardMaterial({
    map: colorTexture,
    normalMap: normalTexture,
  })

  material.onBeforeCompile = (shader) => {
    shader.uniforms.uTime = customUniform.uTime
    shader.vertexShader = shader.vertexShader.replace(
      '#include <common>',
      `
      #include <common>
      mat2 rotate2d(float _angle){
        return mat2(cos(_angle),-sin(_angle),
                sin(_angle),cos(_angle));
      }
      uniform float uTime;
      `
    )

    shader.vertexShader = shader.vertexShader.replace(
      '#include <beginnormal_vertex>',
      `
      #include <beginnormal_vertex>

      float angle = sin(position.y + uTime) * 0.4;
      mat2 rotateMatrix = rotate2d(angle);

      objectNormal.xz = rotateMatrix * objectNormal.xz;
      `
    )

    shader.vertexShader = shader.vertexShader.replace(
      '#include <begin_vertex>',
      `
      #include <begin_vertex>

      transformed.xz = rotateMatrix * transformed.xz;
      `
    )
  }

  leePerrySmithModels.position.z = 5

  const manModels = leePerrySmithModels.children[0] as Mesh<BufferGeometry, MeshStandardMaterial>
  manModels.material = material

  // 设置自定义深度材质
  const depthMaterial = new MeshDepthMaterial({
    depthPacking: RGBADepthPacking,
  })
  depthMaterial.onBeforeCompile = (shader) => {
    shader.uniforms.uTime = customUniform.uTime
    shader.vertexShader = shader.vertexShader.replace(
      '#include <common>',
      `
      #include <common>
      mat2 rotate2d(float _angle){
        return mat2(cos(_angle),-sin(_angle),
                sin(_angle),cos(_angle));
      }
      uniform float uTime;
      `
    )

    shader.vertexShader = shader.vertexShader.replace(
      '#include <begin_vertex>',
      `
      #include <begin_vertex>

      float angle = sin(position.y + uTime) * 0.4;
      mat2 rotateMatrix = rotate2d(angle);

      transformed.xz = rotateMatrix * transformed.xz;
      `
    )
  }

  manModels.customDepthMaterial = depthMaterial
  // manModels.material.wireframe = true
  manModels.castShadow = true
  planeMesh.receiveShadow = true
  directionalLight.castShadow = true
  renderer.value.shadowMap.enabled = true

  scene.add(planeMesh)

  scene.add(leePerrySmithModels)
  scene.add(directionalLight)

  render()
})

onUnmounted(() => {
  gui.destroy()
})
</script>

<template>
  <div class="processing-material" ref="contentRef"></div>
</template>

<style lang="scss" scoped>
.processing-material {
  width: 100%;
  height: 100%;
  position: relative;
}
</style>
